# -*- coding: utf-8 -*-
"""
PDF表格矩形检测器
精确识别PDF文件中包含表格数据的闭合矩形框，用天蓝色实线标记
使用PaddleOCR进行表格内容验证
"""

import cv2
import numpy as np
import fitz  # PyMuPDF
from typing import List, Tuple, Dict, Any
import os
from paddleocr import PaddleOCR

class PDFTableRectangleDetector:
    """PDF表格矩形检测器类 - 检测包含表格数据的闭合矩形框"""
    
    def __init__(self, pdf_path: str):
        """
        初始化检测器
        
        Args:
            pdf_path: PDF文件路径
        """
        self.pdf_path = pdf_path
        self.images = []
        self.all_rectangles = []
        self.table_rectangles = []
        
        # 重新启用PaddleOCR进行表格内容提取
        self.ocr = PaddleOCR(use_textline_orientation=True, lang='ch')
        print("已启用PaddleOCR用于表格内容提取")
        
    def convert_pdf_to_images(self) -> List[np.ndarray]:
        """
        将PDF转换为高分辨率图片
        
        Returns:
            List[np.ndarray]: 转换后的图片列表
        """
        print("步骤1：将PDF转换为图片...")
        doc = fitz.open(self.pdf_path)
        images = []
        
        for page_num in range(len(doc)):
            page = doc.load_page(page_num)
            # 使用高分辨率矩阵转换，提高检测精度
            matrix = fitz.Matrix(2.0, 2.0)  # 2倍缩放，平衡精度和性能
            pix = page.get_pixmap(matrix=matrix)
            img_data = pix.tobytes("png")
            
            # 转换为OpenCV格式
            nparr = np.frombuffer(img_data, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
            images.append(img)
            
            # 保存原始图片
            cv2.imwrite(f"output/page_{page_num + 1}_original.png", img)
            
        doc.close()
        self.images = images
        print(f"成功转换 {len(images)} 页为图片")
        return images
    
    def detect_closed_rectangles(self, img: np.ndarray) -> List[Tuple[int, int, int, int]]:
        """
        检测图片中真实存在的闭合矩形框
        
        Args:
            img: 输入图片
            
        Returns:
            List[Tuple[int, int, int, int]]: 检测到的闭合矩形列表 (x, y, width, height)
        """
        print("步骤2：检测真实存在的闭合矩形框...")
        
        # 转换为灰度图
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        
        # 使用适当的阈值进行二值化
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
        
        # 保存二值化结果用于调试
        cv2.imwrite(f"output/binary_image.png", binary)
        
        # 查找轮廓 - 使用RETR_LIST获取所有轮廓，包括内部轮廓
        contours, _ = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        
        print(f"找到 {len(contours)} 个轮廓")
        
        rectangles = []
        
        for i, contour in enumerate(contours):
            # 计算轮廓的近似多边形
            epsilon = 0.02 * cv2.arcLength(contour, True)
            approx = cv2.approxPolyDP(contour, epsilon, True)
            
            # 检查是否为矩形（4个顶点）
            if len(approx) == 4:
                # 获取边界矩形
                x, y, w, h = cv2.boundingRect(contour)
                
                # 验证是否为有效的矩形
                if self._validate_closed_rectangle(img, x, y, w, h, contour):
                    rectangles.append((x, y, w, h))
                    print(f"发现有效闭合矩形 {len(rectangles)}: ({x}, {y}, {w}, {h})")
        
        # 使用更宽松的过滤条件，主要去除明显的重复
        rectangles = self._filter_rectangles_conservative(rectangles)
        
        self.all_rectangles.extend(rectangles)
        print(f"检测到 {len(rectangles)} 个闭合矩形框")
        
        return rectangles
    
    def detect_table_content(self, img: np.ndarray, 
                           rectangles: List[Tuple[int, int, int, int]], 
                           page_num: int) -> List[Tuple[int, int, int, int]]:
        """
        检测矩形框内是否包含表格数据
        
        Args:
            img: 输入图片
            rectangles: 所有检测到的矩形框
            
        Returns:
            List[Tuple[int, int, int, int]]: 包含表格数据的矩形框列表
        """
        print("步骤3：分析矩形框内容，检测表格数据...")
        
        table_rectangles = []
        
        for i, (x, y, w, h) in enumerate(rectangles):
            # 提取矩形区域
            roi = img[y:y+h, x:x+w]
            
            # 检测是否包含表格
            if self._is_table_content(roi, w, h):
                table_rectangles.append((x, y, w, h))
                print(f"矩形 {i+1} 包含表格数据: ({x}, {y}, {w}, {h})")
            else:
                print(f"矩形 {i+1} 不包含表格数据，已过滤")
        
        self.table_rectangles = table_rectangles
        print(f"最终保留 {len(table_rectangles)} 个包含表格的矩形框")
        
        # 6. 提取并保存表格内容
        extracted_tables = self.extract_table_contents(table_rectangles, page_num)
        
        return extracted_tables
    
    def _is_table_content(self, roi: np.ndarray, width: int, height: int) -> bool:
        """
        判断区域内容是否为标准表格数据（使用新的OpenCV线检测方法）
        
        Args:
            roi: 矩形区域图像
            width: 区域宽度
            height: 区域高度
            
        Returns:
            bool: 是否包含标准表格数据
        """
        if roi.size == 0:
            return False
        
        # 转换为灰度图
        gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 1. 基本尺寸要求 - 标准数据表格应该比较大
        if width < 400 or height < 300:
            return False
        
        # 2. 过滤掉过大的区域（通常是页面边框）
        if width > 1200 or height > 800:
            return False
        
        # 3. 使用新的OpenCV线检测方法
        table_score = self._opencv_line_based_detection(gray, width, height)
        
        # 4. 检测是否为电路图（排除条件）
        is_circuit_diagram = self._detect_circuit_diagram_features(gray)
        
        # 如果是电路图，直接排除
        if is_circuit_diagram:
            return False
        
        # 表格评分需要达到8分以上（提高标准）
        return table_score >= 8
    
    def _opencv_line_based_detection(self, gray: np.ndarray, width: int, height: int) -> int:
        """
        基于OpenCV的线检测方法来判断是否为标准数据表格
        
        Args:
            gray: 灰度图像
            width: 区域宽度
            height: 区域高度
            
        Returns:
            int: 表格评分（0-12分）
        """
        score = 0
        
        # 1. 霍夫线变换检测直线
        edges = cv2.Canny(gray, 50, 150)
        lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100, 
                               minLineLength=width//6, maxLineGap=20)
        
        if lines is None:
            return 0
            
        # 分类水平线和垂直线
        horizontal_lines = []
        vertical_lines = []
        
        for line in lines:
            x1, y1, x2, y2 = line[0]
            
            # 计算线条角度
            if abs(y2 - y1) < 10:  # 水平线（角度接近0度）
                length = abs(x2 - x1)
                if length > width * 0.6:  # 水平线应该跨越大部分宽度
                    horizontal_lines.append((y1 + y2) // 2)  # 存储Y坐标
                    
            elif abs(x2 - x1) < 10:  # 垂直线（角度接近90度）
                length = abs(y2 - y1)
                if length > height * 0.4:  # 垂直线应该跨越相当高度
                    vertical_lines.append((x1 + x2) // 2)  # 存储X坐标
        
        # 2. 评估线条数量和分布
        # 标准表格应该至少有4条水平线（表头+至少3行数据）和3条垂直线（至少3列）
        if len(horizontal_lines) >= 4:
            score += 3
        elif len(horizontal_lines) >= 3:
            score += 2
        elif len(horizontal_lines) >= 2:
            score += 1
        
        if len(vertical_lines) >= 3:
            score += 3
        elif len(vertical_lines) >= 2:
            score += 2
        elif len(vertical_lines) >= 1:
            score += 1
        
        # 3. 检查线条分布的规律性
        if len(horizontal_lines) >= 3:
            horizontal_lines.sort()
            # 计算行间距的一致性
            intervals = [horizontal_lines[i+1] - horizontal_lines[i] for i in range(len(horizontal_lines)-1)]
            if len(intervals) > 1:
                std_dev = np.std(intervals)
                mean_interval = np.mean(intervals)
                if std_dev < mean_interval * 0.3:  # 行间距相对规律
                    score += 2
        
        # 4. 检测文本区域密度
        binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计可能的文本区域
        text_regions = 0
        for contour in contours:
            area = cv2.contourArea(contour)
            if 50 < area < 2000:  # 文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                if 0.2 < aspect_ratio < 15 and w > 10 and h > 6:
                    text_regions += 1
        
        # 表格应该有足够的文本内容
        if text_regions >= 15:
            score += 2
        elif text_regions >= 10:
            score += 1
        
        # 5. 检测表格边界完整性
        border_score = self._detect_complete_borders(gray)
        score += border_score
        
        # 6. 尺寸加分 - 标准数据表格通常有一定的尺寸
        if width > 500 and height > 400:
            score += 1
        
        return score
    
    def _detect_complete_borders(self, gray: np.ndarray) -> int:
        """
        检测表格边界完整性
        
        Args:
            gray: 灰度图像
            
        Returns:
            int: 边界评分（0-2分）
        """
        edges = cv2.Canny(gray, 50, 150)
        h, w = gray.shape
        
        # 检查四个边缘的边界密度
        border_width = 5
        top_border = edges[0:border_width, :]
        bottom_border = edges[-border_width:, :]
        left_border = edges[:, 0:border_width]
        right_border = edges[:, -border_width:]
        
        # 计算边界像素密度
        top_density = np.sum(top_border > 0) / top_border.size
        bottom_density = np.sum(bottom_border > 0) / bottom_border.size
        left_density = np.sum(left_border > 0) / left_border.size
        right_density = np.sum(right_border > 0) / right_border.size
        
        # 统计有明显边界的边数
        clear_borders = sum([
            top_density > 0.15,
            bottom_density > 0.15,
            left_density > 0.15,
            right_density > 0.15
        ])
        
        # 边界完整性评分
        if clear_borders >= 4:
            return 2
        elif clear_borders >= 3:
            return 1
        else:
            return 0
    
    def _detect_balanced_grid_structure(self, gray: np.ndarray) -> bool:
        """平衡的网格结构检测"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检测水平线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//8, 1))
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        
        # 检测垂直线
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//8))
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        
        # 统计线条
        h_contours, _ = cv2.findContours(h_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        v_contours, _ = cv2.findContours(v_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计有效的长线条
        long_h_lines = 0
        for contour in h_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > gray.shape[1] * 0.5:  # 水平线长度要求
                long_h_lines += 1
        
        long_v_lines = 0
        for contour in v_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if h > gray.shape[0] * 0.3:  # 垂直线长度要求
                long_v_lines += 1
        
        # 标准表格至少需要3条水平线和2条垂直线
        return long_h_lines >= 3 and long_v_lines >= 2
    
    def _detect_balanced_text_content(self, gray: np.ndarray) -> bool:
        """平衡的文本内容检测"""
        if gray.size == 0:
            return False
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计文本区域
        text_regions = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 25 < area < 1000:  # 文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                if 0.3 < aspect_ratio < 10 and w > 6 and h > 4:
                    text_regions.append((x, y, w, h))
        
        # 检查文本数量
        if len(text_regions) < 12:  # 标准表格应该有较多文本
            return False
        
        # 检查文本分布的规律性
        y_coords = [y + h//2 for x, y, w, h in text_regions]
        x_coords = [x + w//2 for x, y, w, h in text_regions]
        
        # 分组检查
        y_groups = self._group_coordinates(y_coords, tolerance=20)
        x_groups = self._group_coordinates(x_coords, tolerance=25)
        
        # 至少要有3行3列的布局
        return len(y_groups) >= 3 and len(x_groups) >= 3
    
    def _detect_table_boundaries(self, gray: np.ndarray) -> bool:
        """检测表格边界"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检查边界完整性
        h, w = gray.shape
        top_edge = edges[0:3, :]
        bottom_edge = edges[-3:, :]
        left_edge = edges[:, 0:3]
        right_edge = edges[:, -3:]
        
        # 计算边界密度
        top_density = np.sum(top_edge > 0) / top_edge.size
        bottom_density = np.sum(bottom_edge > 0) / bottom_edge.size
        left_density = np.sum(left_edge > 0) / left_edge.size
        right_density = np.sum(right_edge > 0) / right_edge.size
        
        # 至少两个边界要有明显的边框
        clear_boundaries = sum([
            top_density > 0.1,
            bottom_density > 0.1,
            left_density > 0.1,
            right_density > 0.1
        ])
        
        return clear_boundaries >= 2
    
    def _detect_circuit_diagram_features(self, gray: np.ndarray) -> bool:
        """检测电路图特征（用于排除）"""
        if gray.size == 0:
            return False
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找轮廓
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计圆形和特殊形状（电路图特征）
        circular_shapes = 0
        irregular_shapes = 0
        
        for contour in contours:
            area = cv2.contourArea(contour)
            if 50 < area < 500:  # 电路元件大小范围
                # 检查形状特征
                perimeter = cv2.arcLength(contour, True)
                if perimeter > 0:
                    circularity = 4 * np.pi * area / (perimeter * perimeter)
                    
                    # 圆形度高的可能是电路元件
                    if circularity > 0.7:
                        circular_shapes += 1
                    
                    # 复杂形状也可能是电路元件
                    hull = cv2.convexHull(contour)
                    hull_area = cv2.contourArea(hull)
                    if hull_area > 0:
                        solidity = area / hull_area
                        if solidity < 0.8:  # 形状不规整
                            irregular_shapes += 1
        
        # 如果有较多圆形或不规则形状，可能是电路图
        return circular_shapes > 3 or irregular_shapes > 5
    
    def _detect_regular_table_grid(self, gray: np.ndarray) -> bool:
        """检测规整的表格网格（更严格）"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检测长水平线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//6, 1))
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        
        # 检测长垂直线
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//6))
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        
        # 统计有效的长线条
        h_contours, _ = cv2.findContours(h_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        v_contours, _ = cv2.findContours(v_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        long_h_lines = 0
        for contour in h_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > gray.shape[1] * 0.7:  # 水平线必须跨越70%宽度
                long_h_lines += 1
        
        long_v_lines = 0
        for contour in v_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if h > gray.shape[0] * 0.5:  # 垂直线必须跨越50%高度
                long_v_lines += 1
        
        # 表格必须有足够的长线条形成网格
        return long_h_lines >= 3 and long_v_lines >= 3
    
    def _detect_table_text_pattern(self, gray: np.ndarray) -> bool:
        """检测典型的表格文本分布模式"""
        if gray.size == 0:
            return False
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计文本区域，更严格的尺寸要求
        text_regions = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 30 < area < 1500:  # 调整文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                # 文本宽高比应该合理
                if 0.5 < aspect_ratio < 8 and w > 8 and h > 6:
                    text_regions.append((x, y, w, h))
        
        # 表格应该有大量文本内容
        if len(text_regions) < 15:  # 提高文本区域数量要求
            return False
        
        # 检查文本是否呈规律的网格分布
        y_coords = [y + h//2 for x, y, w, h in text_regions]
        x_coords = [x + w//2 for x, y, w, h in text_regions]
        
        # 更严格的对齐检查
        y_groups = self._group_coordinates(y_coords, tolerance=15)
        x_groups = self._group_coordinates(x_coords, tolerance=25)
        
        # 检查每行是否有足够的文本元素
        valid_rows = 0
        for y_group in y_groups:
            row_texts = [region for region in text_regions 
                        if any(abs((region[1] + region[3]//2) - y) <= 15 for y in y_group)]
            if len(row_texts) >= 3:  # 每行至少3个文本元素
                valid_rows += 1
        
        return len(y_groups) >= 3 and len(x_groups) >= 3 and valid_rows >= 3
    
    def _detect_complete_table_boundaries(self, gray: np.ndarray) -> bool:
        """检测完整的表格边界"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检查四个边界的边框完整性
        h, w = gray.shape
        
        # 检查上下边界
        top_edge = edges[0:5, :]
        bottom_edge = edges[-5:, :]
        left_edge = edges[:, 0:5]
        right_edge = edges[:, -5:]
        
        # 计算边界像素密度
        top_density = np.sum(top_edge > 0) / (5 * w)
        bottom_density = np.sum(bottom_edge > 0) / (5 * w)
        left_density = np.sum(left_edge > 0) / (h * 5)
        right_density = np.sum(right_edge > 0) / (h * 5)
        
        # 至少三个边界要有明显的边框
        clear_boundaries = sum([
            top_density > 0.15,
            bottom_density > 0.15,
            left_density > 0.15,
            right_density > 0.15
        ])
        
        return clear_boundaries >= 3
    
    def _detect_dense_regular_text(self, gray: np.ndarray) -> bool:
        """检测密集且规律的文本排列"""
        if gray.size == 0:
            return False
        
        # 使用更精细的投影分析
        h_projection = np.sum(gray < 200, axis=1)
        v_projection = np.sum(gray < 200, axis=0)
        
        # 计算投影的规律性
        h_mean = np.mean(h_projection)
        v_mean = np.mean(v_projection)
        
        # 统计显著的投影峰值
        h_peaks = []
        v_peaks = []
        
        h_threshold = h_mean * 1.8  # 提高阈值
        for i in range(10, len(h_projection) - 10):  # 避开边界
            if (h_projection[i] > h_threshold and 
                h_projection[i] > h_projection[i-1] and 
                h_projection[i] > h_projection[i+1]):
                # 确保峰值之间有一定距离
                if not h_peaks or min(abs(i - p) for p in h_peaks) >= 20:
                    h_peaks.append(i)
        
        v_threshold = v_mean * 1.5
        for i in range(20, len(v_projection) - 20):
            if (v_projection[i] > v_threshold and
                v_projection[i] > v_projection[i-1] and
                v_projection[i] > v_projection[i+1]):
                if not v_peaks or min(abs(i - p) for p in v_peaks) >= 30:
                    v_peaks.append(i)
        
        # 表格应该有明显的行列结构
        return len(h_peaks) >= 4 and len(v_peaks) >= 3
    
    def _detect_complete_table_grid(self, gray: np.ndarray) -> bool:
        """检测完整的表格网格结构"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检测水平线和垂直线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//8, 1))
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//8))
        
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        
        # 统计有效线条
        h_contours, _ = cv2.findContours(h_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        v_contours, _ = cv2.findContours(v_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 过滤有效的水平线（降低要求）
        valid_h_lines = 0
        for contour in h_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > gray.shape[1] * 0.4:  # 降低水平线长度要求
                valid_h_lines += 1
        
        # 过滤有效的垂直线（降低要求）
        valid_v_lines = 0
        for contour in v_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if h > gray.shape[0] * 0.3:  # 降低垂直线长度要求
                valid_v_lines += 1
        
        # 降低要求：至少3条水平线和2条垂直线
        return valid_h_lines >= 3 and valid_v_lines >= 2
    
    def _detect_standard_table_layout(self, gray: np.ndarray) -> bool:
        """检测标准表格的行列布局"""
        if gray.size == 0:
            return False
        
        # 使用投影分析检测规律的行列结构
        # 水平投影 - 检测行
        h_projection = np.sum(gray < 200, axis=1)
        # 垂直投影 - 检测列
        v_projection = np.sum(gray < 200, axis=0)
        
        # 寻找投影峰值（表格线的位置）
        h_peaks = self._find_projection_peaks(h_projection, min_distance=15)
        v_peaks = self._find_projection_peaks(v_projection, min_distance=20)
        
        # 标准表格至少需要3行（表头+至少2行数据）和3列
        if len(h_peaks) < 3 or len(v_peaks) < 3:
            return False
        
        # 检查行间距是否相对规律（数据行间距应该相似）
        if len(h_peaks) >= 4:
            intervals = [h_peaks[i+1] - h_peaks[i] for i in range(1, len(h_peaks)-1)]
            if len(intervals) > 1:
                interval_std = np.std(intervals)
                interval_mean = np.mean(intervals)
                # 数据行间距应该相对规律
                if interval_std > interval_mean * 0.3:
                    return False
        
        return True
    
    def _detect_clear_table_header(self, gray: np.ndarray) -> bool:
        """检测清晰的表头行"""
        if gray.size == 0:
            return False
        
        # 分析顶部区域（表头通常在顶部）
        header_height = min(gray.shape[0] // 3, 80)  # 扩大表头区域
        header_region = gray[:header_height, :]
        
        # 检测表头区域是否有水平分隔线
        edges = cv2.Canny(header_region, 30, 120)  # 降低边缘检测阈值
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (header_region.shape[1]//6, 1))
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        
        # 检查是否有明显的水平分隔线（降低要求）
        h_line_pixels = np.sum(h_lines > 0)
        if h_line_pixels < header_region.shape[1] * 0.2:  # 降低分隔线要求
            return False
        
        # 检测表头文本密度（放宽范围）
        _, binary = cv2.threshold(header_region, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        text_pixels = np.sum(binary == 0)
        text_ratio = text_pixels / binary.size
        
        # 表头应该有适中的文本密度（放宽范围）
        return 0.02 < text_ratio < 0.4
    
    def _detect_data_rows_content(self, gray: np.ndarray) -> bool:
        """检测数据行内容"""
        if gray.size == 0:
            return False
        
        # 跳过表头区域，分析数据区域
        header_height = min(gray.shape[0] // 3, 80)  # 与表头检测保持一致
        data_region = gray[header_height:, :]
        
        if data_region.size == 0:
            return False
        
        # 二值化处理
        _, binary = cv2.threshold(data_region, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计可能的文本区域（放宽标准）
        text_regions = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 10 < area < 1200:  # 扩大文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                if 0.1 < aspect_ratio < 20 and w > 5 and h > 3:  # 放宽宽高比要求
                    text_regions.append((x, y + header_height, w, h))
        
        # 降低文本区域数量要求
        if len(text_regions) < 4:  # 从6降到4
            return False
        
        # 检查文本区域是否有规律的行列分布
        y_coords = [y + h//2 for x, y, w, h in text_regions]
        x_coords = [x + w//2 for x, y, w, h in text_regions]
        
        # 按坐标分组（放宽分组容差）
        y_groups = self._group_coordinates(y_coords, tolerance=20)  # 增加容差
        x_groups = self._group_coordinates(x_coords, tolerance=30)  # 增加容差
        
        # 降低行列要求：至少2行2列
        return len(y_groups) >= 2 and len(x_groups) >= 2
    
    def _check_content_density(self, gray: np.ndarray) -> bool:
        """检查内容密度的合理性"""
        if gray.size == 0:
            return False
        
        # 二值化
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
        
        # 计算内容像素比例
        content_pixels = np.sum(binary > 0)
        total_pixels = binary.size
        content_ratio = content_pixels / total_pixels
        
        # 标准表格的内容密度应该在合理范围内
        # 不能太空（大片空白）也不能太密（可能是图像而非表格）
        return 0.05 < content_ratio < 0.3
    
    def _detect_internal_grid(self, gray: np.ndarray) -> bool:
        """检测内部网格结构"""
        if gray.size == 0:
            return False
        
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检测水平线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//6, 1))
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        h_count = cv2.countNonZero(h_lines)
        
        # 检测垂直线
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//6))
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        v_count = cv2.countNonZero(v_lines)
        
        # 判断是否有足够的网格线
        min_lines = min(gray.shape[0], gray.shape[1]) // 20
        return h_count > min_lines and v_count > min_lines
    
    def _detect_structured_text(self, gray: np.ndarray) -> bool:
        """检测结构化文本分布"""
        if gray.size == 0:
            return False
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本区域
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 过滤出文本大小的连通组件
        text_regions = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 20 < area < 2000:  # 文本区域大小范围
                text_regions.append(contour)
        
        # 检查文本区域分布是否规律（表格特征）
        if len(text_regions) < 3:
            return False
        
        # 分析文本区域的垂直和水平对齐
        centers = []
        for contour in text_regions:
            M = cv2.moments(contour)
            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])
                centers.append((cx, cy))
        
        if len(centers) < 3:
            return False
        
        # 检查是否有规律的行列分布
        y_coords = [c[1] for c in centers]
        x_coords = [c[0] for c in centers]
        
        # 检查Y坐标分布（行对齐）
        y_groups = self._group_coordinates(y_coords, tolerance=10)
        # 检查X坐标分布（列对齐）
        x_groups = self._group_coordinates(x_coords, tolerance=15)
        
        # 至少有2行2列才认为是结构化文本
        return len(y_groups) >= 2 and len(x_groups) >= 2
    
    def _group_coordinates(self, coords: List[int], tolerance: int = 10) -> List[List[int]]:
        """将相近的坐标分组"""
        if not coords:
            return []
        
        sorted_coords = sorted(coords)
        groups = []
        current_group = [sorted_coords[0]]
        
        for coord in sorted_coords[1:]:
            if coord - current_group[-1] <= tolerance:
                current_group.append(coord)
            else:
                groups.append(current_group)
                current_group = [coord]
        
        groups.append(current_group)
        return groups
    
    def _detect_table_patterns(self, roi: np.ndarray) -> bool:
        """检测表格特有的模式（基于参考图像的特征）"""
        if roi.size == 0:
            return False
        
        # 转换为灰度图
        gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 检测规律的矩形单元格
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
        
        # 查找轮廓
        contours, _ = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计相似大小的矩形数量
        cell_sizes = []
        for contour in contours:
            x, y, w, h = cv2.boundingRect(contour)
            if 15 < w < roi.shape[1]//2 and 10 < h < roi.shape[0]//3:
                cell_sizes.append((w, h))
        
        if len(cell_sizes) < 4:  # 至少需要4个单元格
            return False
        
        # 检查是否有相似大小的单元格（表格特征）
        similar_cells = 0
        for i, (w1, h1) in enumerate(cell_sizes):
            for j, (w2, h2) in enumerate(cell_sizes[i+1:], i+1):
                if (abs(w1 - w2) < w1 * 0.3 and abs(h1 - h2) < h1 * 0.3):
                    similar_cells += 1
        
        # 如果有足够多的相似单元格，认为是表格
        return similar_cells >= len(cell_sizes) // 3
    
    def _validate_closed_rectangle(self, img: np.ndarray, x: int, y: int, w: int, h: int, 
                                 contour: np.ndarray) -> bool:
        """
        验证矩形是否为真实存在的闭合矩形
        
        Args:
            img: 原图
            x, y, w, h: 矩形坐标和尺寸
            contour: 轮廓
            
        Returns:
            bool: 是否为有效的闭合矩形
        """
        # 基本尺寸检查
        min_area = 500  # 降低最小面积要求
        min_width, min_height = 20, 15  # 降低最小宽度和高度要求
        
        area = w * h
        
        # 尺寸过滤
        if area < min_area or w < min_width or h < min_height:
            return False
        
        # 宽高比检查 - 不要太极端
        aspect_ratio = w / h if h > 0 else 0
        if aspect_ratio < 0.1 or aspect_ratio > 20:
            return False
        
        # 检查轮廓的紧密度 - 确保轮廓确实形成了矩形
        rect_area = w * h
        contour_area = cv2.contourArea(contour)
        
        if contour_area < rect_area * 0.5:  # 放宽轮廓面积要求
            return False
        
        # 检查轮廓是否真的接近矩形形状
        hull = cv2.convexHull(contour)
        hull_area = cv2.contourArea(hull)
        
        if hull_area < contour_area * 0.9:  # 放宽凸包面积要求
            return False
        
        return True
    
    def _filter_rectangles_conservative(self, rectangles: List[Tuple[int, int, int, int]]) -> List[Tuple[int, int, int, int]]:
        """
        保守地过滤矩形，只去除明显的重复
        
        Args:
            rectangles: 原始矩形列表
            
        Returns:
            List[Tuple[int, int, int, int]]: 过滤后的矩形列表
        """
        if not rectangles:
            return []
        
        # 只去除高度重叠的矩形（重叠度>90%）
        filtered_rectangles = []
        
        for rect in rectangles:
            is_duplicate = False
            for existing_rect in filtered_rectangles:
                overlap = self._calculate_overlap(rect, existing_rect)
                if overlap > 0.9:  # 只有重叠度很高的才认为是重复
                    is_duplicate = True
                    break
            
            if not is_duplicate:
                filtered_rectangles.append(rect)
        
        return filtered_rectangles
    
    def _calculate_overlap(self, rect1: Tuple[int, int, int, int], 
                          rect2: Tuple[int, int, int, int]) -> float:
        """
        计算两个矩形的重叠度
        
        Args:
            rect1, rect2: 两个矩形
            
        Returns:
            float: 重叠度 (0-1)
        """
        x1, y1, w1, h1 = rect1
        x2, y2, w2, h2 = rect2
        
        # 计算交集
        left = max(x1, x2)
        top = max(y1, y2)
        right = min(x1 + w1, x2 + w2)
        bottom = min(y1 + h1, y2 + h2)
        
        if right < left or bottom < top:
            return 0.0
        
        intersection_area = (right - left) * (bottom - top)
        union_area = w1 * h1 + w2 * h2 - intersection_area
        
        return intersection_area / union_area if union_area > 0 else 0.0
    
    def draw_skyblue_borders(self, img: np.ndarray, 
                          table_rectangles: List[Tuple[int, int, int, int]]) -> np.ndarray:
        """
        用天蓝色实线绘制包含表格的矩形框
        
        Args:
            img: 输入图片
            table_rectangles: 包含表格的矩形列表
            
        Returns:
            np.ndarray: 绘制了天蓝色边框的图片
        """
        print("步骤4：绘制天蓝色表格矩形框...")
        
        result_img = img.copy()
        skyblue_color = (235, 206, 135)  # 天蓝色 BGR格式
        
        for i, (x, y, w, h) in enumerate(table_rectangles):
            # 绘制天蓝色矩形框，使用较粗的线条
            cv2.rectangle(result_img, (x, y), (x + w, y + h), skyblue_color, 4)
            # 添加矩形编号
            cv2.putText(result_img, f"T{i+1}", (x+5, y+20), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.8, skyblue_color, 2)
        
        print(f"成功绘制 {len(table_rectangles)} 个天蓝色表格矩形框")
        return result_img
    
    def visualize_contours(self, img: np.ndarray, binary: np.ndarray) -> np.ndarray:
        """
        可视化所有检测到的轮廓，用于调试
        
        Args:
            img: 原图
            binary: 二值图
            
        Returns:
            np.ndarray: 绘制了轮廓的图片
        """
        contours, _ = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        
        contour_img = img.copy()
        cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)
        
        return contour_img
    
    def validate_table_with_ocr(self, roi: np.ndarray) -> bool:
        """
        使用PaddleOCR验证区域是否为标准表格
        标准表格应包含：表头行 + 数据行的结构化内容
        
        Args:
            roi: 矩形区域图像
            
        Returns:
            bool: 是否为标准表格格式
        """
        if roi.size == 0:
            return False
        
        try:
            # 使用PaddleOCR进行文字识别
            ocr_results = self.ocr.predict(roi)
            
            if not ocr_results or 'dt_polys' not in ocr_results or 'rec_text' not in ocr_results:
                print("    OCR识别: 无法识别文本内容")
                return False
            
            # 提取识别到的文本信息
            texts = []
            boxes = []
            dt_polys = ocr_results['dt_polys']
            rec_texts = ocr_results['rec_text']
            rec_scores = ocr_results['rec_score']
            
            for i, (box, text, score) in enumerate(zip(dt_polys, rec_texts, rec_scores)):
                if score > 0.5:  # 置信度过滤
                    text = text.strip()
                    if text:  # 非空文本
                        texts.append(text)
                        boxes.append(box)
            
            print(f"    OCR识别: 检测到{len(texts)}个文本区域")
            
            if len(texts) < 6:  # 表格至少需要6个文本区域
                print("    OCR验证: 文本区域数量不足")
                return False
            
            # 分析文本布局是否符合表格结构
            table_analysis = self._analyze_table_structure(texts, boxes, roi.shape)
            
            return table_analysis
            
        except Exception as e:
            print(f"    OCR识别出错: {str(e)}")
            return False
    
    def _analyze_table_structure(self, texts: List[str], boxes: List[List], img_shape: Tuple) -> bool:
        """
        分析OCR识别结果是否符合表格结构
        
        Args:
            texts: 识别到的文本列表
            boxes: 对应的边界框坐标
            img_shape: 图像尺寸
            
        Returns:
            bool: 是否符合表格结构
        """
        if len(texts) < 6:
            return False
        
        # 提取文本中心点坐标
        text_centers = []
        for box in boxes:
            # 计算边界框中心点
            box_array = np.array(box)
            center_x = np.mean(box_array[:, 0])
            center_y = np.mean(box_array[:, 1])
            text_centers.append((center_x, center_y))
        
        # 按Y坐标分组（识别行）
        y_coords = [center[1] for center in text_centers]
        row_groups = self._group_coordinates(y_coords, tolerance=15)
        
        # 按X坐标分组（识别列）
        x_coords = [center[0] for center in text_centers]
        col_groups = self._group_coordinates(x_coords, tolerance=20)
        
        print(f"    表格分析: 检测到{len(row_groups)}行, {len(col_groups)}列")
        
        # 表格基本要求：至少3行2列
        if len(row_groups) < 3 or len(col_groups) < 2:
            print("    表格分析: 行列数量不符合要求")
            return False
        
        # 检查是否有表头特征
        has_header = self._detect_table_header(texts, text_centers, row_groups)
        
        # 检查是否有数据行特征
        has_data_rows = self._detect_data_rows(texts, text_centers, row_groups)
        
        print(f"    表格分析: 表头={'是' if has_header else '否'}, 数据行={'是' if has_data_rows else '否'}")
        
        # 标准表格必须同时具备表头和数据行
        return has_header and has_data_rows
    
    def _detect_table_header(self, texts: List[str], centers: List[Tuple], row_groups: List[List]) -> bool:
        """检测表头特征"""
        if not row_groups:
            return False
        
        # 获取第一行的文本（通常是表头）
        first_row_y = min(row_groups, key=lambda group: np.mean(group))
        first_row_y_avg = np.mean(first_row_y)
        
        # 提取第一行的文本
        header_texts = []
        for i, (x, y) in enumerate(centers):
            if abs(y - first_row_y_avg) <= 15:  # 容差范围内
                header_texts.append(texts[i])
        
        if len(header_texts) < 2:
            return False
        
        # 检查表头文本特征
        # 1. 表头通常包含描述性词汇
        header_keywords = ['名称', '编号', '型号', '规格', '数量', '单位', '备注', '序号', 
                          '日期', '时间', '地点', '内容', '类型', '状态', '分类', '项目']
        
        has_header_keywords = False
        for text in header_texts:
            for keyword in header_keywords:
                if keyword in text:
                    has_header_keywords = True
                    break
        
        # 2. 表头文本长度通常较短且相对统一
        avg_header_len = np.mean([len(text) for text in header_texts if text])
        uniform_length = np.std([len(text) for text in header_texts if text]) < 3
        
        return has_header_keywords or (2 <= avg_header_len <= 8 and uniform_length)
    
    def _detect_data_rows(self, texts: List[str], centers: List[Tuple], row_groups: List[List]) -> bool:
        """检测数据行特征"""
        if len(row_groups) < 2:
            return False
        
        # 检查除第一行外的其他行
        data_rows = 0
        for row_y_group in row_groups[1:]:  # 跳过第一行（表头）
            row_y_avg = np.mean(row_y_group)
            
            # 提取该行的文本
            row_texts = []
            for i, (x, y) in enumerate(centers):
                if abs(y - row_y_avg) <= 15:
                    row_texts.append(texts[i])
            
            if len(row_texts) >= 2:  # 至少有2个单元格的行才算数据行
                # 检查是否包含数据特征（数字、字母、标识符等）
                has_data = False
                for text in row_texts:
                    # 包含数字、字母组合或常见的编码格式
                    if any(c.isdigit() for c in text) or \
                       any(c.isalpha() for c in text) or \
                       '-' in text or '_' in text or '.' in text:
                        has_data = True
                        break
                
                if has_data:
                    data_rows += 1
        
        # 至少要有2行数据
        return data_rows >= 2
    
    def _check_basic_grid_structure(self, gray: np.ndarray) -> bool:
        """检查基本的网格结构"""
        # 使用更宽松的网格检测
        edges = cv2.Canny(gray, 30, 100)
        
        # 检测主要的水平线和垂直线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//6, 1))
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//6))
        
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        
        # 统计线条
        h_contours, _ = cv2.findContours(h_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        v_contours, _ = cv2.findContours(v_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 过滤有效线条
        valid_h_lines = 0
        for contour in h_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > gray.shape[1] * 0.3:  # 水平线长度至少占宽度的30%
                valid_h_lines += 1
        
        valid_v_lines = 0
        for contour in v_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if h > gray.shape[0] * 0.2:  # 垂直线长度至少占高度的20%
                valid_v_lines += 1
        
        # 基本要求：至少2条水平线和2条垂直线
        return valid_h_lines >= 2 and valid_v_lines >= 2
    
    def _check_cell_content_distribution(self, roi: np.ndarray) -> bool:
        """检查单元格内容分布"""
        gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计可能的文本区域
        text_areas = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 15 < area < 2000:  # 文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                if 0.1 < aspect_ratio < 15 and w > 5 and h > 3:
                    text_areas.append((x, y, w, h))
        
        # 检查是否有足够的内容区域
        if len(text_areas) < 4:
            return False
        
        # 检查内容分布的规律性
        y_coords = [y + h//2 for x, y, w, h in text_areas]
        
        # 简单的行对齐检查
        y_groups = self._group_coordinates(y_coords, tolerance=20)
        
        # 至少要有2行内容
        return len(y_groups) >= 2
    
    def _check_table_boundaries(self, gray: np.ndarray) -> bool:
        """检查表格边界清晰度"""
        # 检查边缘是否有明显的边界
        edges = cv2.Canny(gray, 50, 150)
        
        # 检查四周边界
        h, w = gray.shape
        
        # 检查边缘区域的边界密度
        top_border = edges[0:3, :]
        bottom_border = edges[-3:, :]
        left_border = edges[:, 0:3]
        right_border = edges[:, -3:]
        
        # 计算边界像素密度
        borders = [top_border, bottom_border, left_border, right_border]
        border_densities = [np.sum(border > 0) / border.size for border in borders]
        
        # 至少两个边有明显边界
        clear_borders = sum([density > 0.05 for density in border_densities])
        return clear_borders >= 2
    
    def _check_reasonable_table_size(self, roi: np.ndarray) -> bool:
        """检查表格尺寸合理性"""
        h, w = roi.shape[:2]
        
        # 表格不能太小（参考图像显示的表格都有一定尺寸）
        min_width = 100
        min_height = 80
        
        # 也不能太大（避免整个页面被当作表格）
        max_width = 800
        max_height = 600
        
        return (min_width <= w <= max_width) and (min_height <= h <= max_height)
    
    def _has_complete_regular_grid(self, gray: np.ndarray) -> bool:
        """检查是否有完整规律的网格结构（参考图像的关键特征）"""
        # 边缘检测
        edges = cv2.Canny(gray, 50, 150)
        
        # 检测水平线和垂直线
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (gray.shape[1]//8, 1))
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, gray.shape[0]//8))
        
        h_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel)
        v_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel)
        
        # 找到水平线和垂直线的轮廓
        h_contours, _ = cv2.findContours(h_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        v_contours, _ = cv2.findContours(v_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 分析线条的规律性
        h_positions = []
        for contour in h_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > gray.shape[1] * 0.5:  # 水平线应该跨越大部分宽度
                h_positions.append(y + h//2)
        
        v_positions = []
        for contour in v_contours:
            x, y, w, h = cv2.boundingRect(contour)
            if h > gray.shape[0] * 0.3:  # 垂直线应该跨越相当的高度
                v_positions.append(x + w//2)
        
        # 检查是否有足够的网格线且分布相对均匀
        if len(h_positions) < 4 or len(v_positions) < 3:  # 至少4行3列
            return False
        
        # 检查线条间距的规律性
        h_positions.sort()
        v_positions.sort()
        
        # 计算间距的一致性
        h_intervals = [h_positions[i+1] - h_positions[i] for i in range(len(h_positions)-1)]
        v_intervals = [v_positions[i+1] - v_positions[i] for i in range(len(v_positions)-1)]
        
        # 间距应该相对规律（标准差不能太大）
        if len(h_intervals) > 1:
            h_std = np.std(h_intervals)
            h_mean = np.mean(h_intervals)
            if h_std > h_mean * 0.5:  # 间距变化不能太大
                return False
        
        return True
    
    def _has_structured_cell_data(self, roi: np.ndarray) -> bool:
        """检查单元格内是否有结构化的数据文本"""
        gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 二值化
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计可能的文本区域
        text_regions = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if 20 < area < 1000:  # 文本区域大小范围
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                # 文本通常有一定的宽高比特征
                if 0.2 < aspect_ratio < 8 and w > 8 and h > 5:
                    text_regions.append((x, y, w, h))
        
        # 检查文本分布的规律性（表格特征）
        if len(text_regions) < 6:  # 至少要有6个文本区域
            return False
        
        # 检查文本区域是否有行列对齐特征
        y_coords = [y + h//2 for x, y, w, h in text_regions]
        x_coords = [x + w//2 for x, y, w, h in text_regions]
        
        # 按Y坐标分组（行对齐）
        y_groups = self._group_coordinates(y_coords, tolerance=15)
        # 按X坐标分组（列对齐）
        x_groups = self._group_coordinates(x_coords, tolerance=20)
        
        # 至少要有3行2列的文本分布
        return len(y_groups) >= 3 and len(x_groups) >= 2
    
    def _has_continuous_grid_lines(self, gray: np.ndarray) -> bool:
        """检查网格线的连续性和完整性"""
        edges = cv2.Canny(gray, 50, 150)
        
        # 使用霍夫线变换检测完整的直线
        lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=50, 
                                minLineLength=gray.shape[1]//4, maxLineGap=10)
        
        if lines is None:
            return False
        
        horizontal_lines = []
        vertical_lines = []
        
        for line in lines:
            x1, y1, x2, y2 = line[0]
            
            # 判断线条方向
            if abs(y2 - y1) < 10:  # 水平线
                length = abs(x2 - x1)
                if length > gray.shape[1] * 0.4:  # 水平线应该足够长
                    horizontal_lines.append(line)
            elif abs(x2 - x1) < 10:  # 垂直线
                length = abs(y2 - y1)
                if length > gray.shape[0] * 0.3:  # 垂直线应该足够长
                    vertical_lines.append(line)
        
        # 需要有足够的水平线和垂直线形成网格
        return len(horizontal_lines) >= 3 and len(vertical_lines) >= 2
    
    def _has_appropriate_content_density(self, gray: np.ndarray) -> bool:
        """检查内容密度是否适中（避免大片空白或过度密集）"""
        # 二值化
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
        
        # 计算非空白像素比例
        content_ratio = np.sum(binary > 0) / binary.size
        
        # 表格内容密度应该在合理范围内
        # 太少说明大片空白，太多说明不是规整表格
        return 0.1 < content_ratio < 0.4
    
    def _has_complete_outer_border(self, gray: np.ndarray) -> bool:
        """检查是否有完整的外边框"""
        # 检查图像边缘是否有边框
        edges = cv2.Canny(gray, 50, 150)
        
        # 检查四个边缘的边框完整性
        top_edge = edges[0:5, :]
        bottom_edge = edges[-5:, :]
        left_edge = edges[:, 0:5]
        right_edge = edges[:, -5:]
        
        # 计算每个边缘的边框像素比例
        top_ratio = np.sum(top_edge > 0) / top_edge.size
        bottom_ratio = np.sum(bottom_edge > 0) / bottom_edge.size
        left_ratio = np.sum(left_edge > 0) / left_edge.size
        right_ratio = np.sum(right_edge > 0) / right_edge.size
        
        # 至少三个边应该有明显的边框
        border_count = sum([ratio > 0.1 for ratio in [top_ratio, bottom_ratio, left_ratio, right_ratio]])
        return border_count >= 3
    
    def _has_regular_row_column_layout(self, gray: np.ndarray) -> bool:
        """检查是否有规律的行列布局"""
        # 投影分析
        h_projection = np.sum(gray < 200, axis=1)  # 水平投影
        v_projection = np.sum(gray < 200, axis=0)  # 垂直投影
        
        # 查找投影峰值（表格的行列分界线）
        h_peaks = self._find_projection_peaks(h_projection)
        v_peaks = self._find_projection_peaks(v_projection)
        
        # 规律的表格应该有多个相对均匀分布的峰值
        return len(h_peaks) >= 3 and len(v_peaks) >= 2
    
    def _find_projection_peaks(self, projection: np.ndarray, min_distance: int = 20) -> List[int]:
        """查找投影数组中的峰值"""
        peaks = []
        threshold = np.mean(projection) * 1.2
        
        for i in range(1, len(projection) - 1):
            if (projection[i] > threshold and 
                projection[i] > projection[i-1] and 
                projection[i] > projection[i+1]):
                # 检查与已有峰值的距离
                if not peaks or min(abs(i - p) for p in peaks) >= min_distance:
                    peaks.append(i)
        
        return peaks
    
    def _has_distinct_header_row(self, gray: np.ndarray) -> bool:
        """检查是否有明显的表头行"""
        # 分析图像上部区域（通常表头在上方）
        header_height = gray.shape[0] // 5  # 上方20%区域
        header_region = gray[:header_height, :]
        
        # 检查表头区域的文本密度
        _, binary = cv2.threshold(header_region, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        text_ratio = np.sum(binary == 0) / binary.size
        
        # 表头通常有较高的文本密度
        return 0.05 < text_ratio < 0.3
    
    def _has_dense_grid_pattern(self, gray: np.ndarray) -> bool:
        """检查是否有密集的网格模式"""
        # 使用形态学操作检测网格密度
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
        
        # 检测小的矩形结构（表格单元格）
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
        morph = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
        
        # 查找轮廓
        contours, _ = cv2.findContours(morph, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        
        # 统计可能的单元格数量
        cell_count = 0
        total_area = gray.shape[0] * gray.shape[1]
        
        for contour in contours:
            area = cv2.contourArea(contour)
            # 单元格大小应该在合理范围内
            if total_area * 0.001 < area < total_area * 0.1:
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                # 单元格应该接近矩形
                if 0.3 < aspect_ratio < 5:
                    cell_count += 1
        
        # 密集表格应该有较多单元格
        return cell_count >= 6
    
    def _has_text_in_grid_cells(self, roi: np.ndarray) -> bool:
        """检查网格单元格中是否有文本内容"""
        gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 检测可能的文本区域
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        
        # 查找文本连通组件
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        text_regions = 0
        for contour in contours:
            area = cv2.contourArea(contour)
            # 文本区域大小判断
            if 10 < area < 500:
                x, y, w, h = cv2.boundingRect(contour)
                aspect_ratio = w / h if h > 0 else 0
                # 文字通常有一定的宽高比
                if 0.1 < aspect_ratio < 10:
                    text_regions += 1
        
        # 至少应该有一定数量的文本区域
        return text_regions >= 4

    def filter_validated_rectangles(self, img: np.ndarray, 
                                   table_rectangles: List[Tuple[int, int, int, int]]) -> List[Tuple[int, int, int, int]]:
        """
        过滤表格矩形，使用PaddleOCR验证是否为真正的表格（表头+数据行）
        
        Args:
            img: 输入图片
            table_rectangles: 原始表格矩形列表
            
        Returns:
            List[Tuple[int, int, int, int]]: 验证通过的表格矩形列表
        """
        print("步骤4：使用PaddleOCR验证表格内容（表头+数据行结构）...")
        
        validated_rectangles = []
        
        for i, (x, y, w, h) in enumerate(table_rectangles):
            # 提取矩形区域
            roi = img[y:y+h, x:x+w]
            
            # 详细的验证过程
            print(f"\n验证矩形 T{i+1} ({x}, {y}, {w}, {h}):")
            
            # 使用PaddleOCR进行表格内容验证
            if self.validate_table_with_ocr(roi):
                validated_rectangles.append((x, y, w, h))
                print(f"  - 结果: OK 通过验证 - 识别为标准表格")
                
                # 保存验证通过的区域用于调试
                cv2.imwrite(f"output/validated_table_{i+1}.png", roi)
            else:
                print(f"  - 结果: NO 未通过验证 - 非标准表格结构")
        
        print(f"\n验证完成，保留 {len(validated_rectangles)} 个符合标准的表格矩形")
        return validated_rectangles

    def process_pdf(self) -> List[str]:
        """
        主要处理流程 - 增加表格验证步骤
        
        Returns:
            List[str]: 输出文件路径列表
        """
        print("开始PDF表格矩形检测处理...")
        print("="*60)
        
        # 创建输出目录
        os.makedirs("output", exist_ok=True)
        
        # 步骤1：转换PDF为图片
        images = self.convert_pdf_to_images()
        
        all_results = []
        
        for page_num, img in enumerate(images):
            print(f"\n处理第 {page_num + 1} 页...")
            
            # 转换为灰度图和二值图用于轮廓检测
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
            
            # 保存处理过程图片
            cv2.imwrite(f"output/page_{page_num + 1}_gray.png", gray)
            cv2.imwrite(f"output/page_{page_num + 1}_binary.png", binary)
            
            # 可视化轮廓
            contour_img = self.visualize_contours(img, binary)
            cv2.imwrite(f"output/page_{page_num + 1}_contours.png", contour_img)
            
            # 步骤2：检测所有闭合矩形
            all_rectangles = self.detect_closed_rectangles(img)
            
            # 保存所有检测到的矩形可视化（绿色标记）
            all_rect_img = img.copy()
            for i, (x, y, w, h) in enumerate(all_rectangles):
                cv2.rectangle(all_rect_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.putText(all_rect_img, f"{i+1}", (x+5, y+20), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
            cv2.imwrite(f"output/page_{page_num + 1}_all_rectangles.png", all_rect_img)
            
            # 步骤3：检测表格内容
            table_rectangles = self.detect_table_content(img, all_rectangles, page_num)
            
            # 保存表格矩形可视化（红色标记）
            table_rect_img = img.copy()
            for i, (x, y, w, h) in enumerate(table_rectangles):
                cv2.rectangle(table_rect_img, (x, y), (x + w, y + h), (0, 0, 255), 3)
                cv2.putText(table_rect_img, f"表格{i+1}", (x+5, y+25), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            cv2.imwrite(f"output/page_{page_num + 1}_table_rectangles.png", table_rect_img)
            
            # 暂时跳过PaddleOCR验证，直接使用检测到的表格矩形
            validated_rectangles = table_rectangles
            
            # 步骤5：绘制验证通过的天蓝色边框
            result_img = self.draw_skyblue_borders(img, validated_rectangles)
            
            # 保存最终结果（只包含验证通过的表格）
            output_path = f"output/page_{page_num + 1}_validated_skyblue_tables.png"
            cv2.imwrite(output_path, result_img)
            all_results.append(output_path)
            
            # 同时保存原始结果用于对比
            original_result_img = self.draw_skyblue_borders(img, table_rectangles)
            cv2.imwrite(f"output/page_{page_num + 1}_original_skyblue_tables.png", original_result_img)
            
            print(f"第 {page_num + 1} 页处理完成，验证结果保存至：{output_path}")
        
        # 输出处理结果统计
        print("\n" + "="*60)
        print("PDF表格矩形检测处理完成！")
        print("="*60)
        print(f"处理页数：{len(all_results)}")
        print(f"检测到的闭合矩形总数：{len(self.all_rectangles)}")
        print(f"识别出的表格矩形总数：{len(self.table_rectangles)}")
        print("\n最终结果文件：")
        for result in all_results:
            print(f"  - {result}")
        
        return all_results
    
    def extract_table_contents(self, table_rectangles: List[Tuple[int, int, int, int]], page_num: int) -> List[Dict]:
        """
        提取表格内容
        
        Args:
            table_rectangles: 检测到的表格矩形框列表
            page_num: 页面编号
            
        Returns:
            List[Dict]: 提取的表格数据
        """
        extracted_tables = []
        
        if not table_rectangles:
            return extracted_tables
        
        img = self.images[page_num]
        
        for i, (x, y, w, h) in enumerate(table_rectangles):
            print(f"正在提取第 {i+1} 个表格的内容...")
            
            # 1. 截取表格区域
            table_roi = img[y:y+h, x:x+w]
            
            # 保存截取的表格图片用于调试
            table_crop_path = f"output/page_{page_num + 1}_table_{i+1}_crop.png"
            cv2.imwrite(table_crop_path, table_roi)
            print(f"表格截图已保存：{table_crop_path}")
            
            # 2. 使用PaddleOCR进行文本识别
            table_data = self.parse_table_with_paddleocr(table_roi, page_num + 1, i + 1)
            
            if table_data:
                extracted_tables.append(table_data)
        
        # 3. 保存提取结果为txt文件
        if extracted_tables:
            self.save_table_data_to_txt(extracted_tables, page_num + 1)
        
        return extracted_tables
    
    def parse_table_with_paddleocr(self, table_img: np.ndarray, page_num: int, table_num: int) -> Dict:
        """
        使用PaddleOCR解析表格内容
        
        Args:
            table_img: 表格图像
            page_num: 页面编号
            table_num: 表格编号
            
        Returns:
            Dict: 解析的表格数据
        """
        try:
            # 使用PaddleOCR进行文本检测和识别
            results = self.ocr.ocr(table_img)
            
            if not results or not results[0]:
                print(f"页面 {page_num} 表格 {table_num} 未检测到文本")
                return None
            
            # 提取文本和位置信息
            text_boxes = []
            for line in results[0]:
                try:
                    bbox = line[0]  # 边界框坐标
                    text_info = line[1]  # 文本信息
                    
                    # 处理不同的返回格式
                    if isinstance(text_info, tuple) and len(text_info) >= 2:
                        text = text_info[0]  # 识别的文本
                        confidence = text_info[1]  # 置信度
                    elif isinstance(text_info, str):
                        text = text_info
                        confidence = 1.0  # 默认置信度
                    else:
                        print(f"未知的文本格式: {text_info}")
                        continue
                    
                    if confidence > 0.5:  # 只保留高置信度的文本
                        # 计算文本框中心点
                        center_x = int((bbox[0][0] + bbox[2][0]) / 2)
                        center_y = int((bbox[0][1] + bbox[2][1]) / 2)
                        width = int(bbox[2][0] - bbox[0][0])
                        height = int(bbox[2][1] - bbox[0][1])
                        
                        text_boxes.append({
                            'text': text,
                            'center_x': center_x,
                            'center_y': center_y,
                            'width': width,
                            'height': height,
                            'confidence': confidence
                        })
                except Exception as e:
                    print(f"处理文本框时出错: {e}, 跳过该文本框")
                    continue
            
            print(f"检测到 {len(text_boxes)} 个文本框")
            
            # 分析表格结构并组织数据
            structured_data = self.analyze_table_structure(text_boxes, page_num, table_num)
            
            return structured_data
            
        except Exception as e:
            print(f"PaddleOCR解析表格时出错: {e}")
            return None
    
    def analyze_table_structure(self, text_boxes: List[Dict], page_num: int, table_num: int) -> Dict:
        """
        分析表格结构并组织数据
        
        Args:
            text_boxes: 文本框列表
            page_num: 页面编号
            table_num: 表格编号
            
        Returns:
            Dict: 结构化的表格数据
        """
        if not text_boxes:
            return None
        
        # 按Y坐标对文本框进行分组（行）
        text_boxes.sort(key=lambda x: x['center_y'])
        
        # 分组行
        rows = []
        current_row = [text_boxes[0]]
        row_tolerance = 15  # Y坐标容差
        
        for i in range(1, len(text_boxes)):
            if abs(text_boxes[i]['center_y'] - current_row[0]['center_y']) <= row_tolerance:
                current_row.append(text_boxes[i])
            else:
                # 按X坐标排序当前行
                current_row.sort(key=lambda x: x['center_x'])
                rows.append(current_row)
                current_row = [text_boxes[i]]
        
        # 添加最后一行
        if current_row:
            current_row.sort(key=lambda x: x['center_x'])
            rows.append(current_row)
        
        print(f"识别出 {len(rows)} 行数据")
        
        # 提取表头和数据行
        if len(rows) == 0:
            return None
        
        # 第一行作为表头
        headers = [box['text'] for box in rows[0]]
        
        # 其余行作为数据
        data_rows = []
        for row in rows[1:]:
            data_row = [box['text'] for box in row]
            # 确保每行的列数与表头一致
            while len(data_row) < len(headers):
                data_row.append('')
            data_rows.append(data_row[:len(headers)])  # 截断多余列
        
        table_data = {
            'page': f"page_{page_num}",
            'table_num': table_num,
            'headers': headers,
            'data_rows': data_rows,
            'row_count': len(data_rows)
        }
        
        print(f"表头: {headers}")
        print(f"数据行数: {len(data_rows)}")
        
        return table_data
    
    def save_table_data_to_txt(self, extracted_tables: List[Dict], page_num: int):
        """
        保存表格数据为txt格式
        
        Args:
            extracted_tables: 提取的表格数据列表
            page_num: 页面编号
        """
        output_file = f"output/page_{page_num}_extracted_table_data.txt"
        
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write("提取的表格数据：\n")
            f.write("=" * 50 + "\n\n")
            
            for table_data in extracted_tables:
                f.write(f"页面: {table_data['page']}\n")
                f.write("-" * 30 + "\n")
                f.write(f"表格 {table_data['table_num']}:\n")
                f.write(f"  表头: {table_data['headers']}\n")
                f.write(f"  数据行数: {table_data['row_count']}\n")
                
                for i, row in enumerate(table_data['data_rows'], 1):
                    f.write(f"  行{i}: {row}\n")
                f.write("\n")
        
        print(f"表格数据已保存到: {output_file}")

def main():
    """主函数"""
    pdf_file_path = "123.pdf"
    
    # 检查PDF文件是否存在
    if not os.path.exists(pdf_file_path):
        print(f"错误：未找到PDF文件 '{pdf_file_path}'")
        print("请确保PDF文件在当前目录下")
        return
    
    print("启动PDF表格矩形检测系统")
    print("="*60)
    print("功能：检测包含表格数据的矩形框，用天蓝色实线标记")
    print("="*60)
    
    # 先清理输出文件夹
    if os.path.exists("output"):
        import glob
        for f in glob.glob("output/*"):
            if os.path.isfile(f):
                os.remove(f)
    
    # 创建检测器并开始处理
    detector = PDFTableRectangleDetector(pdf_file_path)
    results = detector.process_pdf()
    
    # 处理完成提示
    print(f"\n处理完成！共生成 {len(results)} 个结果文件")
    print("所有文件已保存在 'output' 文件夹中")
    print("\n关键输出文件：")
    print("- page_X_skyblue_tables.png: 最终结果（天蓝色表格边框）")
    print("- page_X_all_rectangles.png: 所有检测到的矩形（绿色标记）")
    print("- page_X_table_rectangles.png: 识别的表格矩形（红色标记）")
    print("- page_X_contours.png: 轮廓检测结果")

if __name__ == "__main__":
    main()